<?php

namespace Luracast\Restler\Filter;

use Luracast\Restler\Defaults;
use Luracast\Restler\iFilter;
use Luracast\Restler\iUseAuthentication;
use Luracast\Restler\RestException;

/**
 * Describe the purpose of this class/interface/trait
 *
 * @category Framework
 * @package restler
 * @author R.Arul Kumaran <arul@luracast.com>
 * @copyright 2010 Luracast
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link http://luracast.com/products/restler/
 * @version 3.0.0rc5
 */
class RateLimit implements iFilter, iUseAuthentication {
	/**
	 *
	 * @var \Luracast\Restler\Restler;
	 */
	public $restler;
	/**
	 *
	 * @var int
	 */
	public static $usagePerUnit = 1200;
	/**
	 *
	 * @var int
	 */
	public static $authenticatedUsagePerUnit = 5000;
	/**
	 *
	 * @var string
	 */
	public static $unit = 'hour';
	/**
	 *
	 * @var string group the current api belongs to
	 */
	public static $group = 'common';
	protected static $units = array (
			'second' => 1,
			'minute' => 60,
			'hour' => 3600, // 60*60 seconds
			'day' => 86400, // 60*60*24 seconds
			'week' => 604800, // 60*60*24*7 seconds
			'month' => 2592000 
	) // 60*60*24*30 seconds
;
	
	/**
	 *
	 * @var array all paths beginning with any of the following will be excluded
	 *      from documentation
	 */
	public static $excludedPaths = array (
			'resources' 
	);
	
	/**
	 *
	 * @param string $unit        	
	 * @param int $usagePerUnit        	
	 * @param int $authenticatedUsagePerUnit
	 *        	set it to false to give unlimited access
	 *        	
	 * @throws \InvalidArgumentException
	 * @return void
	 */
	public static function setLimit($unit, $usagePerUnit, $authenticatedUsagePerUnit = null) {
		static::$unit = $unit;
		static::$usagePerUnit = $usagePerUnit;
		static::$authenticatedUsagePerUnit = is_null ( $authenticatedUsagePerUnit ) ? $usagePerUnit : $authenticatedUsagePerUnit;
	}
	public function __isAllowed() {
		if (static::$authenticatedUsagePerUnit == static::$usagePerUnit)
			return $this->check ();
		return null;
	}
	public function __setAuthenticationStatus($isAuthenticated = false) {
		header ( 'X-Auth-Status: ' . ($isAuthenticated ? 'true' : 'false') );
		$this->check ( $isAuthenticated );
	}
	private static function validate($unit) {
		if (! isset ( static::$units [$unit] ))
			throw new \InvalidArgumentException ( 'Rate Limit time unit should be ' . implode ( '|', array_keys ( static::$units ) ) . '.' );
	}
	private function check($isAuthenticated = false) {
		$path = $this->restler->url;
		foreach ( static::$excludedPaths as $exclude ) {
			if (empty ( $exclude ) && empty ( $path )) {
				return true;
			} elseif (0 === strpos ( $path, $exclude )) {
				return true;
			}
		}
		static::validate ( static::$unit );
		$timeUnit = static::$units [static::$unit];
		$maxPerUnit = $isAuthenticated ? static::$authenticatedUsagePerUnit : static::$usagePerUnit;
		if ($maxPerUnit) {
			$user = Defaults::$userIdentifierClass;
			if (! method_exists ( $user, 'getUniqueIdentifier' )) {
				throw new \UnexpectedValueException ( '`Defaults::$userIdentifierClass` must implement `iIdentifyUser` interface' );
			}
			$id = "RateLimit_" . $maxPerUnit . '_per_' . static::$unit . '_for_' . static::$group . '_' . $user::getUniqueIdentifier ();
			$lastRequest = $this->restler->cache->get ( $id, true ) ?  : array (
					'time' => 0,
					'used' => 0 
			);
			$time = $lastRequest ['time'];
			$diff = time () - $time; // in seconds
			$used = $lastRequest ['used'];
			
			header ( "X-RateLimit-Limit: $maxPerUnit per " . static::$unit );
			if ($diff >= $timeUnit) {
				$used = 1;
				$time = time ();
			} elseif ($used >= $maxPerUnit) {
				header ( "X-RateLimit-Remaining: 0" );
				$wait = $timeUnit - $diff;
				sleep ( 1 );
				throw new RestException ( 429, 'Rate limit of ' . $maxPerUnit . ' request' . ($maxPerUnit > 1 ? 's' : '') . ' per ' . static::$unit . ' exceeded. Please wait for ' . static::duration ( $wait ) . '.' );
			} else {
				$used ++;
			}
			$remainingPerUnit = $maxPerUnit - $used;
			header ( "X-RateLimit-Remaining: $remainingPerUnit" );
			$this->restler->cache->set ( $id, array (
					'time' => $time,
					'used' => $used 
			) );
		}
		return true;
	}
	private function duration($secs) {
		$units = array (
				'week' => ( int ) ($secs / 86400 / 7),
				'day' => $secs / 86400 % 7,
				'hour' => $secs / 3600 % 24,
				'minute' => $secs / 60 % 60,
				'second' => $secs % 60 
		);
		
		$ret = array ();
		
		// $unit = 'days';
		foreach ( $units as $k => $v ) {
			if ($v > 0) {
				$ret [] = $v > 1 ? "$v {$k}s" : "$v $k";
				// $unit = $k;
			}
		}
		$i = count ( $ret ) - 1;
		if ($i) {
			$ret [$i] = 'and ' . $ret [$i];
		}
		return implode ( ' ', $ret ); // ." $unit.";
	}
}